#include <windows.h>
#include <mmsystem.h>
#include <iostream>
#include <string>
#include <fstream>
#include <pocketsphinx.h>
#include "Recorder.h"
#include "SoundPlayer.h"
#include "VoiceRecognizer.h"


// Versao antiga do reconhecedor de voz
// Reconhece apenas o buffer inteiro
int old_vr()
{
	const int TIME = 4000;	//miliseconds
	const int NUMPTS = (int) 16000 * TIME / 1000;   // 20 seconds
	int sampleRate = 16000;
	short int waveIn[NUMPTS];   // 'short int' is a 16-bit type; I request 16-bit samples below
	short int waveIn2[NUMPTS];
	// for 8-bit capture, you'd use 'unsigned char' or 'BYTE' 8-bit types

	HWAVEIN      hWaveIn;
	WAVEHDR      WaveInHdr,WaveInHdr2;
	MMRESULT result;

	// Specify recording parameters
	WAVEFORMATEX pFormat;
	pFormat.wFormatTag=WAVE_FORMAT_PCM;     // simple, uncompressed format
	pFormat.nChannels=1;                    //  1=mono, 2=stereo
	pFormat.nSamplesPerSec=sampleRate;      // 16000
	pFormat.nAvgBytesPerSec=sampleRate*2;   // = nSamplesPerSec * n.Channels * wBitsPerSample/8
	pFormat.nBlockAlign=2;                  // = n.Channels * wBitsPerSample/8
	pFormat.wBitsPerSample=16;              //  16 for high quality, 8 for telephone-grade
	pFormat.cbSize=0;

	result = waveInOpen(&hWaveIn, WAVE_MAPPER,&pFormat, 0L, 0L, WAVE_FORMAT_DIRECT);
	if (result)
	{
		char fault[256];
		waveInGetErrorText(result, fault, 256);
		MessageBox(0,fault, "Failed to open waveform input device.", MB_OK | MB_ICONEXCLAMATION);
		return 0;
	}

///////////////////////////////////////////////////////////////////////
// prepara buffers
///////////////////////////////////////////////////////////////////////
	// Set up and prepare header for input
	WaveInHdr.lpData = (LPSTR)waveIn;
	WaveInHdr.dwBufferLength = NUMPTS*2;
	WaveInHdr.dwBytesRecorded=0;
	WaveInHdr.dwUser = 0L;
	WaveInHdr.dwFlags = 0L;
	WaveInHdr.dwLoops = 0L;
	waveInPrepareHeader(hWaveIn, &WaveInHdr, sizeof(WAVEHDR));

	// Insert a wave input buffer
	result = waveInAddBuffer(hWaveIn, &WaveInHdr, sizeof(WAVEHDR));
	if (result)
	{
		MessageBox(0, "Failed to read block from device", NULL, MB_OK | MB_ICONEXCLAMATION);
		return 0;
	}

	//// PREPARE THE SECOND BUFFER
	//// Set up and prepare header for input
	//WaveInHdr2.lpData = (LPSTR)waveIn2;
	//WaveInHdr2.dwBufferLength = NUMPTS*2;
	//WaveInHdr2.dwBytesRecorded=0;
	//WaveInHdr2.dwUser = 0L;
	//WaveInHdr2.dwFlags = 0L;
	//WaveInHdr2.dwLoops = 0L;
	//waveInPrepareHeader(hWaveIn, &WaveInHdr2, sizeof(WAVEHDR));

	//// Insert a wave input buffer
	//result = waveInAddBuffer(hWaveIn, &WaveInHdr2, sizeof(WAVEHDR));
	//if (result)
	//{
	//	MessageBox(0, "Failed to read block from device", NULL, MB_OK | MB_ICONEXCLAMATION);
	//	return 0;
	//}

/////////////////////////////////////////////////////////////////

	std::cout	<< "This example allows your to speak for 4 seconds, "
				<< "then replay what you've said and finally try to recognize "
				<< "using the models given by PocketSphinx." << std::endl;
	system("PAUSE");

	std::cout << "Speak now...";

	// Commence sampling input
	result = waveInStart(hWaveIn);
	if (result)
	{
		MessageBox(0, "Failed to start recording", NULL, MB_OK | MB_ICONEXCLAMATION);
		return 0;
	}


	// Wait until finished recording
	while (waveInUnprepareHeader(hWaveIn, &WaveInHdr, sizeof(WAVEHDR))==WAVERR_STILLPLAYING)
	{Sleep(10);};

	//while (waveInUnprepareHeader(hWaveIn, &WaveInHdr2, sizeof(WAVEHDR))==WAVERR_STILLPLAYING)
	//{};

	waveInClose(hWaveIn);

	std::cout << "Stop speaking!" << std::endl;
	std::cout << "Playing...";

	HWAVEOUT     hWaveOut;
	MMRESULT hRes = waveOutOpen(&hWaveOut, WAVE_MAPPER, &pFormat, 0L, 0L, WAVE_FORMAT_DIRECT);

	// Play all buffers
	WAVEHDR * ptrBuffersS[] = { &WaveInHdr, /* &WaveInHdr2,*/ NULL };
	WAVEHDR ** ptrBuffers = ptrBuffersS;
	while (*ptrBuffers)
	{
		hRes = waveOutPrepareHeader(hWaveOut, *ptrBuffers, sizeof(WAVEHDR));

		hRes = waveOutWrite(hWaveOut, *ptrBuffers, sizeof(WAVEHDR));
		do {} while (waveOutUnprepareHeader(hWaveOut, *ptrBuffers, sizeof(WAVEHDR))==WAVERR_STILLPLAYING);

		ptrBuffers++;

		//int a;
		//std::cin >> a;
	}


	waveOutClose(hWaveOut);

	std::cout << "Playback finished!" << std::endl;


	std::cout << "Recognizing..." << std::endl;

	/////////////////////////
	ps_decoder_t *ps;
	cmd_ln_t *config;
	FILE *fh;
	char const *hyp, *uttid;
	int rv;
	int32 score;

	std::ifstream ifs("..\\config.txt");
	std::string modelDir;
	std::getline(ifs, modelDir);
	ifs.close();


	config = cmd_ln_init(NULL, ps_args(), TRUE,
			     "-hmm", (modelDir + "/hmm/en_US/hub4wsj_sc_8k").c_str(),
			     //"-lm", (modelDir + "/lm/en/turtle.DMP").c_str(),
			     //"-dict", (modelDir + "/lm/en/turtle.dic").c_str(),
			     "-lm", (modelDir + "/9986.lm").c_str(),
			     "-dict", (modelDir + "/9986.dic").c_str(),
			     NULL);
	if (config == NULL)	return 1;

	ps = ps_init(config);
	if (ps == NULL)	return 1;

	// aumenta prioridade da thread e inicia temporizador
	int oldPriority = GetThreadPriority(GetCurrentThread());
	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
	long start_time = GetTickCount();

    rv = ps_start_utt(ps, "goforward");
	if (rv < 0) return 1;

	short int * buffer = waveIn;
	while (buffer < waveIn + NUMPTS) {
        size_t nsamp = 320;
        rv = ps_process_raw(ps, buffer, nsamp, FALSE, FALSE);

		buffer += 320;
    }
    rv = ps_end_utt(ps);


	if (rv < 0)	return 1;
	hyp = ps_get_hyp(ps, &score, &uttid);
	if (hyp == NULL) return 1;
	
	// Calcula tempo gasto e restaura prioridade da thread
	long elapsed_time = GetTickCount() - start_time;
	SetThreadPriority(GetCurrentThread(), oldPriority);

	printf("[elapsed_time=%d ms] Recognized (%d) : %s\n", elapsed_time, score, hyp);


    ps_free(ps);
	
	return 0;
}



// Varios buffers
int several_buffers()
{
	const int NUMBUFFERS = 10;
	const int TIME = 500;	//miliseconds
	const int NUMPTS = (int) 16000 * TIME / 1000;   // 20 seconds
	int sampleRate = 16000;


	// for 8-bit capture, you'd use 'unsigned char' or 'BYTE' 8-bit types

	HWAVEIN      hWaveIn;
	MMRESULT result;

	// Specify recording parameters
	WAVEFORMATEX pFormat;
	pFormat.wFormatTag=WAVE_FORMAT_PCM;     // simple, uncompressed format
	pFormat.nChannels=1;                    //  1=mono, 2=stereo
	pFormat.nSamplesPerSec=sampleRate;      // 16000
	pFormat.nAvgBytesPerSec=sampleRate*2;   // = nSamplesPerSec * n.Channels * wBitsPerSample/8
	pFormat.nBlockAlign=2;                  // = n.Channels * wBitsPerSample/8
	pFormat.wBitsPerSample=16;              //  16 for high quality, 8 for telephone-grade
	pFormat.cbSize=0;

	result = waveInOpen(&hWaveIn, WAVE_MAPPER, &pFormat, 0L, 0L, WAVE_FORMAT_DIRECT);
	if (result)
	{
		char fault[256];
		waveInGetErrorText(result, fault, 256);
		MessageBox(0,fault, "Failed to open waveform input device.", MB_OK | MB_ICONEXCLAMATION);
		return 0;
	}

///////////////////////////////////////////////////////////////////////
// prepara buffers
///////////////////////////////////////////////////////////////////////
	// Set up and prepare header for input
	std::vector <WAVEHDR *> headers;

	for (int i = 0; i < NUMBUFFERS; i++)
	{
		WAVEHDR * WaveInHdr = new WAVEHDR;
		WaveInHdr->lpData = (LPSTR) new short int [NUMPTS];
		WaveInHdr->dwBufferLength = NUMPTS*2;
		WaveInHdr->dwBytesRecorded=0;
		WaveInHdr->dwUser = 0L;
		WaveInHdr->dwFlags = 0L;
		WaveInHdr->dwLoops = 0L;
		headers.push_back(WaveInHdr);

		result = waveInPrepareHeader(hWaveIn, WaveInHdr, sizeof(WAVEHDR));
		if (result) throw int(result);

		// Insert a wave input buffer
		result = waveInAddBuffer(hWaveIn, WaveInHdr, sizeof(WAVEHDR));
		if (result)
		{
			MessageBox(0, "Failed to read block from device", NULL, MB_OK | MB_ICONEXCLAMATION);
			return 0;
		}
	}

/////////////////////////////////////////////////////////////////

	std::cout	<< "This example allows your to speak for 4 seconds, "
				<< "then replay what you've said and finally try to recognize "
				<< "using the models given by PocketSphinx." << std::endl;
	system("PAUSE");

	std::cout << "Speak now...";

	// Commence sampling input
	result = waveInStart(hWaveIn);
	if (result)
	{
		MessageBox(0, "Failed to start recording", NULL, MB_OK | MB_ICONEXCLAMATION);
		return 0;
	}

	for (int i = 0; i < NUMBUFFERS; i++)
	{
		// Wait until finished recording
		while (waveInUnprepareHeader(hWaveIn, headers[i], sizeof(WAVEHDR))==WAVERR_STILLPLAYING)
		{
			Sleep(10);
		};
		std::cout << ".";
	}
	std::cout << std::endl;

	waveInClose(hWaveIn);

	std::cout << "Stop speaking!" << std::endl;
	std::cout << "Playing...";

	HWAVEOUT     hWaveOut;
	MMRESULT hRes = waveOutOpen(&hWaveOut, WAVE_MAPPER, &pFormat, 0L, 0L, WAVE_FORMAT_DIRECT);

	// Play all buffers
	for (int i = 0; i < NUMBUFFERS; i++)
	{
		hRes = waveOutPrepareHeader(hWaveOut, headers[i], sizeof(WAVEHDR));

		hRes = waveOutWrite(hWaveOut, headers[i], sizeof(WAVEHDR));
		while (waveOutUnprepareHeader(hWaveOut, headers[i], sizeof(WAVEHDR))==WAVERR_STILLPLAYING)
		{
			Sleep(10);
		}

		std::cout << ".";
	}
	std::cout << std::endl;


	waveOutClose(hWaveOut);

	std::cout << "Playback finished!" << std::endl;


	system("PAUSE");
	return 0;
}



// Versao do reconhecedor que reconhece palavras separadas
int each_word_vr()
{
	const int TIME = 4000;	//miliseconds
	const int NUMPTS = (int) 16000 * TIME / 1000;   // 20 seconds
	int sampleRate = 16000;
	short int waveIn[NUMPTS];


	// for 8-bit capture, you'd use 'unsigned char' or 'BYTE' 8-bit types

	HWAVEIN      hWaveIn;
	MMRESULT result;

	// Specify recording parameters
	WAVEFORMATEX pFormat;
	pFormat.wFormatTag=WAVE_FORMAT_PCM;     // simple, uncompressed format
	pFormat.nChannels=1;                    //  1=mono, 2=stereo
	pFormat.nSamplesPerSec=sampleRate;      // 16000
	pFormat.nAvgBytesPerSec=sampleRate*2;   // = nSamplesPerSec * n.Channels * wBitsPerSample/8
	pFormat.nBlockAlign=2;                  // = n.Channels * wBitsPerSample/8
	pFormat.wBitsPerSample=16;              //  16 for high quality, 8 for telephone-grade
	pFormat.cbSize=0;

	result = waveInOpen(&hWaveIn, WAVE_MAPPER, &pFormat, 0L, 0L, WAVE_FORMAT_DIRECT);
	if (result)
	{
		char fault[256];
		waveInGetErrorText(result, fault, 256);
		MessageBox(0,fault, "Failed to open waveform input device.", MB_OK | MB_ICONEXCLAMATION);
		return 0;
	}

///////////////////////////////////////////////////////////////////////
// prepara buffer
///////////////////////////////////////////////////////////////////////
	// Set up and prepare header for input
	WAVEHDR * WaveInHdr = new WAVEHDR;
	WaveInHdr->lpData = (LPSTR) waveIn;
	WaveInHdr->dwBufferLength = NUMPTS*2;
	WaveInHdr->dwBytesRecorded=0;
	WaveInHdr->dwUser = 0L;
	WaveInHdr->dwFlags = 0L;
	WaveInHdr->dwLoops = 0L;

	result = waveInPrepareHeader(hWaveIn, WaveInHdr, sizeof(WAVEHDR));
	if (result) throw int(result);

	// Insert a wave input buffer
	result = waveInAddBuffer(hWaveIn, WaveInHdr, sizeof(WAVEHDR));
	if (result)
	{
		MessageBox(0, "Failed to read block from device", NULL, MB_OK | MB_ICONEXCLAMATION);
		return 0;
	}

/////////////////////////////////////////////////////////////////

	std::cout	<< "This example allows your to speak for 4 seconds, "
				<< "then replay what you've said and finally try to recognize "
				<< "using the models given by PocketSphinx." << std::endl;
	system("PAUSE");

	std::cout << "Speak now...";

	// Commence sampling input
	result = waveInStart(hWaveIn);
	if (result)
	{
		MessageBox(0, "Failed to start recording", NULL, MB_OK | MB_ICONEXCLAMATION);
		return 0;
	}

	// Wait until finished recording
	while (waveInUnprepareHeader(hWaveIn, WaveInHdr, sizeof(WAVEHDR))==WAVERR_STILLPLAYING)
	{
		Sleep(10);
	};

	waveInClose(hWaveIn);

	std::cout << "Stop speaking!" << std::endl;
	std::cout << "Playing...";

	HWAVEOUT     hWaveOut;
	MMRESULT hRes = waveOutOpen(&hWaveOut, WAVE_MAPPER, &pFormat, 0L, 0L, WAVE_FORMAT_DIRECT);

	// Play buffer
	hRes = waveOutPrepareHeader(hWaveOut, WaveInHdr, sizeof(WAVEHDR));

	hRes = waveOutWrite(hWaveOut, WaveInHdr, sizeof(WAVEHDR));
	while (waveOutUnprepareHeader(hWaveOut, WaveInHdr, sizeof(WAVEHDR))==WAVERR_STILLPLAYING)
	{
		Sleep(10);
	}

	waveOutClose(hWaveOut);

	std::cout << "Playback finished!" << std::endl;


	std::cout << "Recognizing..." << std::endl;

	/////////////////////////
	ps_decoder_t *ps;
	cmd_ln_t *config;
	FILE *fh;
	char const *hyp, *uttid;
	int rv;
	int32 score;

	std::ifstream ifs("..\\config.txt");
	std::string modelDir;
	std::getline(ifs, modelDir);
	ifs.close();


	std::ofstream ofs("output.txt");


	config = cmd_ln_init(NULL, ps_args(), TRUE,
			     "-hmm", (modelDir + "/hmm/en_US/hub4wsj_sc_8k").c_str(),
			     //"-lm", (modelDir + "/lm/en/turtle.DMP").c_str(),
			     //"-dict", (modelDir + "/lm/en/turtle.dic").c_str(),
			     "-lm", (modelDir + "/9986.lm").c_str(),
			     "-dict", (modelDir + "/9986.dic").c_str(),
			     NULL);
	if (config == NULL)	return 1;

	ps = ps_init(config);
	if (ps == NULL)	return 1;

	// aumenta prioridade da thread e inicia temporizador
	int oldPriority = GetThreadPriority(GetCurrentThread());
	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
	long start_time = GetTickCount();

    rv = ps_start_utt(ps, "goforward");
	if (rv < 0) return 1;

	bool restart = false;
	short int * buffer = waveIn;
	while (buffer < waveIn + NUMPTS) {
		restart = false;
        size_t nsamp = 1000;
        rv = ps_process_raw(ps, buffer, nsamp, FALSE, FALSE);

		ofs << "Buffer offset: " << std::hex << buffer << std::endl << std::dec;

		ps_seg_t * iter = ps_seg_iter(ps, &score);
		bool wasWordIdentified = false, wasSilIdentified = false;
		std::string identifiedWord = "";
		while (iter)
		{
			std::string word = ps_seg_word(iter);
			int startframe, endframe;
			ps_seg_frames(iter, &startframe, &endframe);
			ofs << word << "(" << startframe << "," << endframe << ") ";

			if (word.find_first_of('<')==std::string::npos && word[0]!='+')
			{
				identifiedWord = word;
				wasWordIdentified = true;
			}
			if (wasWordIdentified && word=="<sil>") wasSilIdentified = true;


			if (wasWordIdentified && wasSilIdentified && word=="</s>")
			{
				ofs << std::endl << "Identified word: " << identifiedWord << std::endl;//seria enviada para a aplicacao

			    rv = ps_end_utt(ps);
				if (rv < 0)	return 1;
				
				rv = ps_start_utt(ps, "goforward");
				if (rv < 0) return 1;

				restart = true;
			}
			if (restart) break;
			iter = ps_seg_next(iter);
		}
		ofs << std::endl;

		if (restart) continue;

		//ps_seg_free(iter);

		buffer += 1000;
    }
	ofs << "END\n";
    rv = ps_end_utt(ps);
	if (rv < 0)	return 1;

	hyp = ps_get_hyp(ps, &score, &uttid);
	if (hyp == NULL) return 1;
	
	// Calcula tempo gasto e restaura prioridade da thread
	long elapsed_time = GetTickCount() - start_time;
	SetThreadPriority(GetCurrentThread(), oldPriority);

	printf("[elapsed_time=%d ms] Recognized (%d) : %s\n", elapsed_time, score, hyp);


	ofs.close();
    ps_free(ps);

	return 0;
}

//#define PLAYER


// Audio continuo: input e output
int continuous_rec()
{
	// Gravacao continua
	int bufferTime_ms = 25;
	int numOfBuffers  = 20;

	try {
		Recorder rec(bufferTime_ms, numOfBuffers);

#ifdef PLAYER
		SoundPlayer player(bufferTime_ms, 2);
		player.start();
		rec.setSoundBufferConsumer(player);
#else
		VoiceRecognizer recog("..\\config.txt", bufferTime_ms);
		recog.start();
		rec.setSoundBufferConsumer(recog);

		class CommandHandler : public ICommandHandler
		{
		public:
			void execute(const std::string & word)
			{
				printf("Recognized: %s\n", word.c_str());
			}
		} commandHandler;
		recog.setCommandHandler(commandHandler);
#endif	

		rec.start();
		
		while (!GetAsyncKeyState(VK_ESCAPE)) Sleep(1);

		rec.tryToTerminate();
		rec.join();

#ifdef PLAYER
		player.tryToTerminate();
		player.join();
#else
		recog.tryToTerminate();
		recog.join();
#endif


#if 0
		////////// play the whole sound buffer at once
		WAVEHDR WaveInHdr;
		WaveInHdr.lpData = (LPSTR) rec.soundBuffer;
		WaveInHdr.dwBufferLength = rec.bufferLength * 2;
		WaveInHdr.dwBytesRecorded = 0;
		WaveInHdr.dwUser = 0L;
		WaveInHdr.dwFlags = 0L;
		WaveInHdr.dwLoops = 0L;

		std::cout << "Playing...";

		HWAVEOUT hWaveOut;
		MMRESULT hRes = waveOutOpen(&hWaveOut, WAVE_MAPPER, &rec.pFormat, 0L, 0L, WAVE_FORMAT_DIRECT);

		hRes = waveOutPrepareHeader(hWaveOut, &WaveInHdr, sizeof(WAVEHDR));

		hRes = waveOutWrite(hWaveOut, &WaveInHdr, sizeof(WAVEHDR));
		while (waveOutUnprepareHeader(hWaveOut, &WaveInHdr, sizeof(WAVEHDR))==WAVERR_STILLPLAYING)
		{
			Sleep(10);
		}

		waveOutClose(hWaveOut);

		std::cout << "Playback finished!" << std::endl;
#endif

	}
	catch (const char * s)
	{
		std::cout << s;
	}

	return 0;
}

